/* ****************************************************************************
 *
 * Copyright (c) Microsoft Corporation. 
 *
 * This source code is subject to terms and conditions of the Microsoft Public License. A 
 * copy of the license can be found in the License.html file at the root of this distribution. If 
 * you cannot locate the  Microsoft Public License, please send an email to 
 * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
 * by the terms of the Microsoft Public License.
 *
 * You must not remove this notice, or any other, from this software.
 *
 *
 * ***************************************************************************/

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.SymbolStore;
using System.IO;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;

using Microsoft.Scripting.Generation;
using Microsoft.Scripting.Runtime;
using Microsoft.Scripting.Utils;

namespace Microsoft.Scripting.Ast {

    /// <summary>
    /// LambdaCompiler is responsible for compiling individual lambda (LambdaExpression). The complete tree may
    /// contain multiple lambdas, the Compiler class is reponsible for compiling the whole tree, individual
    /// lambdas are then compiled by the LambdaCompiler.
    /// </summary>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")] // TODO: fix
    internal partial class LambdaCompiler : IDisposable {

        struct ReturnBlock {
            internal Slot returnValue;
            internal Label returnStart;
        }

        // TODO: readonly
        private bool _dynamicMethod;

        /// <summary>
        /// The compiler which contains the compilation-wide information
        /// such as other lambdas and their Compilers.
        /// </summary>
        private readonly Compiler _compiler;

        /// <summary>
        /// The generator information for the generator being compiled.
        /// This is null if the current lambda is not a generator
        /// </summary>
        private readonly GeneratorInfo _generatorInfo;

        private readonly ILGen _ilg;
        private readonly TypeGen _typeGen;

        private readonly MethodBase _method;

        private readonly ListStack<Targets> _targets = new ListStack<Targets>();

        private Nullable<ReturnBlock> _returnBlock;

        // reference to the function's closure: first argument or null
        private Slot _closureSlot;

        // The currently active variable scope
        private ScopeInfo _scope;


        private readonly ReadOnlyCollection<ParameterExpression> _parameters;

        /// <summary>
        /// Argument slots
        /// 
        /// This list contains _all_ arguments on the underlying method builder (except for the
        /// "this"). There are two views on the list. First provides the raw view (shows all
        /// arguments), the second view provides view of the arguments which are in the original
        /// lambda (so first argument, which may be closure argument, is skipped in that case)
        /// 
        /// The two views are provided by code:GetArgumentSlot (raw) and
        /// code:GetLambdaArgumentSlot (lambda arguments only)
        /// </summary>
        private readonly Slot[] _argumentSlots;
        private readonly int _firstLambdaArgument;

        // State for emitting debug symbols
        // TODO: can we make it readonly?
        private bool _emitDebugSymbols;
        private ISymbolDocumentWriter _debugSymbolWriter;

        private readonly ConstantPool _constantPool;

        /// <summary>
        /// Provides access to runtime constants (raw objects) which have been
        /// cached in the constant pool. Dictionary uses reference equality.
        /// </summary>
        private Dictionary<object, Slot> _constantPoolCache;
        
        // true if emitting generator body, false otherwise
        private bool _generatorBody;

        // Slot that stores the number of the label to go to.
        private Slot _gotoRouter;


        private const int FinallyExitsNormally = 0;
        private const int BranchForReturn = 1;
        private const int BranchForBreak = 2;
        private const int BranchForContinue = 3;

        private LambdaCompiler(
            Compiler compiler,
            LambdaExpression lambda,
            TypeGen typeGen,
            MethodBase mi,
            ILGenerator ilg,
            IList<Type> paramTypes,
            ConstantPool constantPool,
            bool closure,
            bool emitDebugSymbols) {

            ContractUtils.Requires(constantPool == null || mi.IsStatic, "constantPool");

            _typeGen = typeGen;
            _method = mi;

            // Create the ILGen instance, debug or not
#if !SILVERLIGHT
            if (Snippets.Shared.ILDebug) {
                _ilg = CreateDebugILGen(ilg, typeGen, mi, paramTypes);
            } else {
                _ilg = new ILGen(ilg, typeGen);
            }
#else
            _ilg = new ILGen(ilg);
#endif

            // Create the argument array
            int thisOffset = mi.IsStatic ? 0 : 1;
            _argumentSlots = new Slot[paramTypes.Count];
            for (int i = 0; i < _argumentSlots.Length; i++) {
                _argumentSlots[i] = new ArgSlot(i + thisOffset, paramTypes[i], _ilg);
            }

            if (constantPool != null || closure) {
                Debug.Assert(_argumentSlots.Length > 0 && _argumentSlots[0].Type == typeof(Closure));
                // Adjust the lambda vs. raw view of the arguments
                _firstLambdaArgument = 1;
                _closureSlot = _argumentSlots[0];
            }

            // Create/initialize constant pool
            if (constantPool != null) {
                _constantPool = constantPool;
                _constantPool.SetCodeGen(this, new FieldSlot(_closureSlot, typeof(Closure).GetField("Constants")));
            } else {
                _constantPool = new ConstantPool();
                _constantPool.SetCodeGen(this, null);
            }

            // Initialize _info and _compiler
            if (lambda != null) {
                if (compiler == null) {
                    // No compiler, analyze the tree to create one (possibly rewriting lambda)
                    compiler = new Compiler(AnalyzeLambda(ref lambda));
                }
                _scope = compiler.GetScopeInfo(lambda);
                Debug.Assert(_scope != null);

                if (lambda.NodeType == ExpressionType.Generator) {
                    _generatorInfo = compiler.GetGeneratorInfo(lambda);
                }

                _parameters = lambda.Parameters;
            }
            _compiler = compiler;

            Debug.Assert(!emitDebugSymbols || _typeGen != null && _typeGen.AssemblyGen.IsDebuggable);
            _emitDebugSymbols = emitDebugSymbols;

            NoteCompilerCreation(mi);
        }

        public override string ToString() {
            return _method.ToString();
        }

        #region Properties

        private Compiler Compiler {
            get { return _compiler; }
        }

        internal ILGen IL {
            get { return _ilg; }
        }

        /// <summary>
        /// Gets the TypeGen object which this Compiler is emitting into.  TypeGen can be
        /// null if the method is a dynamic method.
        /// </summary>
        internal TypeGen TypeGen {
            get {
                return _typeGen;
            }
        }

        internal MethodBase Method {
            get {
                return _method;
            }
        }

        private bool IsDynamicMethod {
            get {
                return _method is DynamicMethod;
            }
        }

        /// <summary>
        /// Gets a list which can be used to inject references to objects from IL.  
        /// </summary>
        internal ConstantPool ConstantPool {
            get {
                return _constantPool;
            }
        }

        internal Slot ClosureSlot {
            get {
                return _closureSlot;
            }
        }

        private TargetBlockType BlockType {
            get {
                if (_targets.Count == 0) return TargetBlockType.Normal;
                Targets t = _targets.Peek();
                return t.BlockType;
            }
        }

        private Label? BlockContinueLabel {
            get {
                if (_targets.Count == 0) return Targets.NoLabel;
                Targets t = _targets.Peek();
                return t.ContinueLabel;
            }
        }

        internal bool IsGeneratorBody {
            get {
                return _generatorBody;
            }
            private set {
                _generatorBody = value;
            }
        }

        private const int GotoRouterNone = -1;
        private const int GotoRouterYielding = -2;

        private Slot GotoRouter {
            get {
                return _gotoRouter;
            }
            set {
                _gotoRouter = value;
            }
        }

        #endregion

        #region Compiler entry point

        /// <summary>
        /// Compiler entry point, used by TreeCompiler
        /// </summary>
        /// <param name="lambda">LambdaExpression to compile.</param>
        /// <param name="method">Product of compilation</param>
        /// <param name="delegateType">Type of the delegate to create</param>
        /// <param name="emitDebugSymbols">True to emit debug symbols, false otherwise</param>
        /// <returns>The compiled delegate.</returns>
        internal static Delegate CompileLambda(LambdaExpression lambda, Type delegateType, bool emitDebugSymbols, out MethodInfo method) {
            // 1. Create signature
            List<Type> types;
            List<string> names;
            string name;
            ComputeSignature(lambda, out types, out names, out name);

            // 2. Create lambda compiler
            LambdaCompiler c = CreateDynamicLambdaCompiler(
                null, // compiler
                lambda,
                name,
                lambda.ReturnType,
                types,
                null, // parameter names
                new ConstantPool(),
                false, // closure
                emitDebugSymbols
            );

            // 3. Emit
            c.EmitBody();

            c.Finish();

            // 4. Return the delegate.
            return c.CreateDelegate(delegateType, out method);
        }

        internal static T CompileLambda<T>(LambdaExpression lambda, out MethodInfo method) {
            return (T)(object)CompileLambda(lambda, typeof(T), false, out method);
        }

        internal static T CompileLambda<T>(LambdaExpression lambda) {
            MethodInfo method;
            return CompileLambda<T>(lambda, out method);
        }

        internal static Delegate CompileLambda(LambdaExpression lambda) {
            MethodInfo method;
            return CompileLambda(lambda, lambda.Type, false, out method);
        }

        /// <summary>
        /// Used by ScriptCode for generating the top level target
        /// </summary>
        internal static T CompileLambda<T>(LambdaExpression lambda, bool emitDebugSymbols) {
            MethodInfo method;
            return (T)(object)CompileLambda(lambda, typeof(T), emitDebugSymbols, out method);
        }

        /// <summary>
        /// This compiles a lambda to a method. It's designed only for optimized scopes
        /// TODO: remove TypeGen parameter
        /// </summary>
        internal static void CompileLambda(LambdaExpression lambda, TypeGen tg, MethodBuilder mb, bool emitDebugSymbols) {

            List<Type> types;
            List<string> names;
            string lambdaName;
            ComputeSignature(lambda, out types, out names, out lambdaName);

            LambdaCompiler lc = new LambdaCompiler(
                null,
                lambda,
                tg,
                mb,
                mb.GetILGenerator(),
                types,
                null,
                false,
                emitDebugSymbols
            );

            lc.EmitBody();
            lc.Finish();
        }

        #endregion

        private static AnalyzedTree AnalyzeLambda(ref LambdaExpression lambda) {
            DumpLambda(lambda);

            // first re-write all DynamicSite's into normal nodes
            lambda = (LambdaExpression)new DynamicNodeRewriter().RewriteNode(lambda);

            // then spill the stack for any exception handling blocks or other
            // constructs which require entering w/ an empty stack
            lambda = StackSpiller.AnalyzeLambda(lambda);

            // finally bind any variable references across the lambdas
            AnalyzedTree at = VariableBinder.Bind(lambda);

            DumpLambda(lambda);

            return at;
        }

        [Conditional("DEBUG")]
        private static void DumpLambda(LambdaExpression lambda) {
            AstWriter.Dump(lambda, lambda.Name);
        }

        private void PushExceptionBlock(TargetBlockType type, Slot returnFlag) {
            if (_targets.Count == 0) {
                _targets.Push(new Targets(Targets.NoLabel, Targets.NoLabel, type, returnFlag, null));
            } else {
                Targets t = _targets.Peek();
                _targets.Push(new Targets(t.BreakLabel, t.ContinueLabel, type, returnFlag ?? t.FinallyReturns, null));
            }
        }

        private void PushTargets(Label? breakTarget, Label? continueTarget, LabelTarget label) {
            if (_targets.Count == 0) {
                _targets.Push(new Targets(breakTarget, continueTarget, BlockType, null, label));
            } else {
                Targets t = _targets.Peek();
                TargetBlockType bt = t.BlockType;
                if (bt == TargetBlockType.Finally && label != null) {
                    bt = TargetBlockType.LoopInFinally;
                }
                _targets.Push(new Targets(breakTarget, continueTarget, bt, t.FinallyReturns, label));
            }
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "type")]
        private void PopTargets(TargetBlockType type) {
            Targets t = _targets.Pop();
            Debug.Assert(t.BlockType == type);
        }

        private void PopTargets() {
            _targets.Pop();
        }

        // TODO: Cleanup, hacky!!!
        private void CheckAndPushTargets(LabelTarget label) {
            for (int i = _targets.Count - 1; i >= 0; i--) {
                if (_targets[i].Label == label) {
                    PushTargets(_targets[i].BreakLabel, _targets[i].ContinueLabel, null);
                    return;
                }
            }

            throw new InvalidOperationException("Statement not on the stack");
        }

        private void EmitBreak() {
            Targets t = _targets.Peek();
            int finallyIndex = -1;
            switch (t.BlockType) {
                default:
                case TargetBlockType.Normal:
                case TargetBlockType.LoopInFinally:
                    if (t.BreakLabel.HasValue)
                        _ilg.Emit(OpCodes.Br, t.BreakLabel.Value);
                    else
                        throw new InvalidOperationException();
                    break;
                case TargetBlockType.Try:
                case TargetBlockType.Else:
                case TargetBlockType.Catch:
                    for (int i = _targets.Count - 1; i >= 0; i--) {
                        if (_targets[i].BlockType == TargetBlockType.Finally) {
                            finallyIndex = i;
                            break;
                        }

                        if (_targets[i].BlockType == TargetBlockType.LoopInFinally ||
                            !_targets[i].BreakLabel.HasValue)
                            break;
                    }

                    if (finallyIndex == -1) {
                        if (t.BreakLabel.HasValue)
                            _ilg.Emit(OpCodes.Leave, t.BreakLabel.Value);
                        else
                            throw new InvalidOperationException();
                    } else {
                        if (!_targets[finallyIndex].LeaveLabel.HasValue)
                            _targets[finallyIndex].LeaveLabel = _ilg.DefineLabel();

                        _ilg.EmitInt(LambdaCompiler.BranchForBreak);
                        _targets[finallyIndex].FinallyReturns.EmitSet(_ilg);

                        _ilg.Emit(OpCodes.Leave, _targets[finallyIndex].LeaveLabel.Value);
                    }
                    break;
                case TargetBlockType.Finally:
                    _ilg.EmitInt(LambdaCompiler.BranchForBreak);
                    t.FinallyReturns.EmitSet(_ilg);
                    _ilg.Emit(OpCodes.Endfinally);
                    break;
            }
        }

        private void EmitContinue() {
            Targets t = _targets.Peek();
            switch (t.BlockType) {
                default:
                case TargetBlockType.Normal:
                case TargetBlockType.LoopInFinally:
                    if (t.ContinueLabel.HasValue)
                        _ilg.Emit(OpCodes.Br, t.ContinueLabel.Value);
                    else
                        throw new InvalidOperationException();
                    break;
                case TargetBlockType.Try:
                case TargetBlockType.Else:
                case TargetBlockType.Catch:
                    if (t.ContinueLabel.HasValue)
                        _ilg.Emit(OpCodes.Leave, t.ContinueLabel.Value);
                    else
                        throw new InvalidOperationException();
                    break;
                case TargetBlockType.Finally:
                    _ilg.EmitInt(LambdaCompiler.BranchForContinue);
                    t.FinallyReturns.EmitSet(_ilg);
                    _ilg.Emit(OpCodes.Endfinally);
                    break;
            }
        }

        private void EmitReturn() {
            int finallyIndex = -1;
            switch (BlockType) {
                default:
                case TargetBlockType.Normal:
                    _ilg.Emit(OpCodes.Ret);
                    break;
                case TargetBlockType.Catch:
                case TargetBlockType.Try:
                case TargetBlockType.Else:
                    // with has it's own finally block, so no need to search...
                    for (int i = _targets.Count - 1; i >= 0; i--) {
                        if (_targets[i].BlockType == TargetBlockType.Finally) {
                            finallyIndex = i;
                            break;
                        }
                    }

                    EnsureReturnBlock();
                    Debug.Assert(_returnBlock.HasValue);
                    if (CompilerHelpers.GetReturnType(_method) != typeof(void)) {
                        _returnBlock.Value.returnValue.EmitSet(_ilg);
                    }

                    if (finallyIndex == -1) {
                        // emit the real return
                        _ilg.Emit(OpCodes.Leave, _returnBlock.Value.returnStart);
                    } else {
                        // need to leave into the inner most finally block,
                        // the finally block will fall through and check
                        // the return value.
                        if (!_targets[finallyIndex].LeaveLabel.HasValue)
                            _targets[finallyIndex].LeaveLabel = _ilg.DefineLabel();

                        _ilg.EmitInt(LambdaCompiler.BranchForReturn);
                        _targets[finallyIndex].FinallyReturns.EmitSet(_ilg);

                        _ilg.Emit(OpCodes.Leave, _targets[finallyIndex].LeaveLabel.Value);
                    }
                    break;
                case TargetBlockType.LoopInFinally:
                case TargetBlockType.Finally: {
                        Targets t = _targets.Peek();
                        EnsureReturnBlock();
                        if (CompilerHelpers.GetReturnType(_method) != typeof(void)) {
                            _returnBlock.Value.returnValue.EmitSet(_ilg);
                        }
                        // Assert check ensures that those who pushed the block with finallyReturns as null 
                        // should not yield in their lambdas.
                        Debug.Assert(t.FinallyReturns != null);
                        _ilg.EmitInt(LambdaCompiler.BranchForReturn);
                        t.FinallyReturns.EmitSet(_ilg);
                        _ilg.Emit(OpCodes.Endfinally);
                        break;
                    }
            }
        }

        private void EmitImplicitCast(Type from, Type to) {
            if (!TryEmitImplicitCast(from, to)) {
                throw new ArgumentException(String.Format("Cannot cast from '{0}' to '{1}'", from, to));
            }
        }

        private void EmitCast(Type from, Type to) {
            ContractUtils.RequiresNotNull(from, "from");
            ContractUtils.RequiresNotNull(to, "to");

            if (!TryEmitExplicitCast(from, to)) {
                throw new ArgumentException(String.Format("Cannot cast from '{0}' to '{1}'", from, to));
            }
        }

        private bool TryEmitImplicitCast(Type from, Type to) {
            if (from.IsValueType && to == typeof(object)) {
                EmitBoxing(from);
                return true;
            } else {
                return _ilg.TryEmitImplicitCast(from, to);
            }
        }

        private bool TryEmitExplicitCast(Type from, Type to) {
            if (from.IsValueType && to == typeof(object)) {
                EmitBoxing(from);
                return true;
            } else {
                return _ilg.TryEmitExplicitCast(from, to);
            }
        }

        /// <summary>
        /// Boxes the value of the stack. No-op for reference types. Void is converted to a null reference. For almost all value types this method
        /// will box them in the standard way.  Int32 and Boolean are handled with optimized conversions
        /// that reuse the same object for small values.  For Int32 this is purely a performance
        /// optimization.  For Boolean this is use to ensure that True and False are always the same
        /// objects.
        /// </summary>
        /// <param name="type"></param>
        internal void EmitBoxing(Type type) {
            ContractUtils.RequiresNotNull(type, "type");
            Debug.Assert(typeof(void).IsValueType);

            if (type == typeof(int)) {
                _ilg.EmitCall(typeof(RuntimeHelpers), "Int32ToObject");
            } else if (type == typeof(bool)) {
                _ilg.EmitCall(typeof(RuntimeHelpers), "BooleanToObject");
            } else {
                _ilg.EmitBoxing(type);
            }
        }

        private void EmitReturnValue() {
            EnsureReturnBlock();
            if (CompilerHelpers.GetReturnType(_method) != typeof(void)) {
                _returnBlock.Value.returnValue.EmitGet(_ilg);
            }
        }

        private void EmitReturn(Expression expr) {
            if (IsGeneratorBody) {
                EmitReturnInGenerator(expr);
            } else {
                if (expr == null) {
                    Debug.Assert(CompilerHelpers.GetReturnType(_method) == typeof(void));
                } else {
                    Type result = CompilerHelpers.GetReturnType(_method);
                    Debug.Assert(result.IsAssignableFrom(expr.Type));
                    EmitExpression(expr);
                    if (!TypeUtils.CanAssign(result, expr.Type)) {
                        EmitImplicitCast(expr.Type, result);
                    }
                }
                EmitReturn();
            }
        }

        private void EmitReturnInGenerator(Expression expr) {
            EmitSetGeneratorReturnValue(expr);

            _ilg.EmitInt(0);
            EmitReturn();
        }

        private void EmitYield(Expression expr, YieldTarget target) {
            ContractUtils.RequiresNotNull(expr, "expr");

            EmitSetGeneratorReturnValue(expr);
            EmitUpdateGeneratorLocation(target.Index);

            // Mark that we are yielding, which will ensure we skip
            // all of the finally bodies that are on the way to exit

            _ilg.EmitInt(GotoRouterYielding);
            GotoRouter.EmitSet(_ilg);

            _ilg.EmitInt(1);
            EmitReturn();

            _ilg.MarkLabel(target.EnsureLabel(this));
            // Reached the routing destination, set router to GotoRouterNone
            _ilg.EmitInt(GotoRouterNone);
            GotoRouter.EmitSet(_ilg);
        }

        private void EmitSetGeneratorReturnValue(Expression expr) {
            GetLambdaArgumentSlot(1).EmitGet(_ilg);
            EmitExpressionAsObjectOrNull(expr);
            _ilg.Emit(OpCodes.Stind_Ref);
        }

        private void EmitUpdateGeneratorLocation(int index) {
            GetLambdaArgumentSlot(0).EmitGet(_ilg);
            _ilg.EmitInt(index);
            _ilg.EmitFieldSet(typeof(Generator).GetField("location"));
        }

        private void EmitGetGeneratorLocation() {
            GetLambdaArgumentSlot(0).EmitGet(_ilg);
            _ilg.EmitFieldGet(typeof(Generator), "location");
        }

        private void EmitSequencePointNone() {
            EmitPosition(SourceLocation.None, SourceLocation.None);
        }

        private void EmitPosition(SourceLocation start, SourceLocation end) {
            Debug.Assert(_emitDebugSymbols);

            if (!start.IsValid || !end.IsValid) {
                start = SourceLocation.None;
                end = SourceLocation.None;
            }

            if (_debugSymbolWriter != null) {
                _ilg.MarkSequencePoint(_debugSymbolWriter, start.Line, start.Column, end.Line, end.Column);
            }

            _ilg.Emit(OpCodes.Nop);
        }

        internal Slot GetNamedLocal(Type type, string name) {
            ContractUtils.RequiresNotNull(type, "type");
            ContractUtils.RequiresNotNull(name, "name");

            LocalBuilder lb = _ilg.DeclareLocal(type);
            if (_emitDebugSymbols) {
                lb.SetLocalSymInfo(name);
            }
            return new LocalSlot(lb, _ilg);
        }        

        private void EnsureReturnBlock() {
            if (!_returnBlock.HasValue) {
                ReturnBlock val = new ReturnBlock();

                if (CompilerHelpers.GetReturnType(_method) != typeof(void)) {
                    val.returnValue = GetNamedLocal(CompilerHelpers.GetReturnType(_method), "retval");
                }
                val.returnStart = _ilg.DefineLabel();

                _returnBlock = val;
            }
        }

        internal void Finish() {
            Debug.Assert(_targets.Count == 0);

            if (_returnBlock.HasValue) {
                _ilg.MarkLabel(_returnBlock.Value.returnStart);
                if (CompilerHelpers.GetReturnType(_method) != typeof(void)) {
                    _returnBlock.Value.returnValue.EmitGet(_ilg);
                }
                _ilg.Emit(OpCodes.Ret);
            }

            if (_dynamicMethod) {
                CreateDelegateMethodInfo();
            }
        }

        // Used only by OptimizedModuleGenerator
        internal void EmitArgGet(int index) {
            ContractUtils.Requires(index >= 0 && index < Int32.MaxValue, "index");

            if (_method == null || !_method.IsStatic) {
                // making room for this
                index++;
            }

            _ilg.EmitLoadArg(index);
        }

        private void EmitClosureCreation(Slot constantPool) {
            // new Closure(constantPool, currentEnvironment)
            if (constantPool != null) {
                constantPool.EmitGet(_ilg);
            } else {
                _ilg.EmitNull();
            }
            _scope.EmitNearestEnvironment(this);
            _ilg.EmitNew(typeof(Closure).GetConstructor(new Type[] { typeof(object[]), typeof(object[]) }));
        }

        /// <summary>
        /// Emits code which creates new instance of the delegateType delegate.
        /// 
        /// Since the delegate is getting closed over the "Closure" argument, this
        /// cannot be used with virtual/instance methods (delegateFunction must be static method)
        /// </summary>
        private void EmitDelegateConstruction(LambdaCompiler delegateFunction, Type delegateType, bool closure) {
            ContractUtils.RequiresNotNull(delegateFunction, "delegateFunction");
            ContractUtils.RequiresNotNull(delegateType, "delegateType");

            if (delegateFunction.Method is DynamicMethod || delegateFunction._constantPool.IsBound) {
                Slot method = _constantPool.AddData(delegateFunction.CreateDelegateMethodInfo(), typeof(MethodInfo));
                Slot data = _constantPool.AddData(delegateFunction._constantPool.Data, typeof(object[]));

                method.EmitGet(_ilg);                   // method
                _ilg.Emit(OpCodes.Ldtoken, delegateType);    // delegate (as RuntimeTypeHandler)
                EmitClosureCreation(data);
                _ilg.EmitCall(typeof(RuntimeHelpers).GetMethod("CreateDynamicClosure"));
                _ilg.Emit(OpCodes.Castclass, delegateType);
            } else {
                if (closure) {
                    EmitClosureCreation(null);
                } else {
                    _ilg.EmitNull();
                }
                _ilg.Emit(OpCodes.Ldftn, (MethodInfo)delegateFunction.Method);
                _ilg.Emit(OpCodes.Newobj, (ConstructorInfo)(delegateType.GetMember(".ctor")[0]));
            }
        }

        /// <summary>
        /// Returns index-th 'raw' argument on the lambda being compiled.
        /// This includes possible argument at index 0 which is the closure
        /// for the delegate being built.
        /// 
        /// For argument access which only takes into account the actual (lambda) arguments,
        /// use code:GetLambdaArgumentSlot
        /// </summary>
        private Slot GetArgumentSlot(int index) {
            return _argumentSlots[index];
        }

        /// <summary>
        /// Returns the index-th argument. This method provides access to the actual arguments
        /// defined on the lambda itself, and excludes the possible 0-th closure argument.
        /// </summary>
        internal Slot GetLambdaArgumentSlot(int index) {
            return GetArgumentSlot(index + _firstLambdaArgument);
        }

        /// <summary>
        /// Gets the argument slot corresponding to the parameter at the given
        /// index. Assumes that the method takes a certain number of prefix
        /// arguments, followed by the real parameters stored in Parameters
        /// </summary>
        internal Slot GetLogicalArgument(int index) {
            return _argumentSlots[index + (_argumentSlots.Length - _parameters.Count)];
        }

        /// <summary>
        /// Gets the argument slot corresponding to the parameter expression
        /// given. Assumes that the method takes a certain number of prefix
        /// arguments, followed by the real parameters stored in Parameters
        /// </summary>
        internal Slot GetLogicalArgument(ParameterExpression param) {
            return GetLogicalArgument(_parameters.IndexOf(param));
        }

        internal int GetLambdaArgumentSlotCount() {
            return _argumentSlots.Length - _firstLambdaArgument;
        }

        internal ReadOnlyCollection<ParameterExpression> Parameters {
            get { return _parameters; }
        }

        internal MethodInfo CreateDelegateMethodInfo() {
            if (_method is DynamicMethod) {
                return (MethodInfo)_method;
            } else if (_method is MethodBuilder) {
                MethodBuilder mb = _method as MethodBuilder;
                Type methodType = _typeGen.FinishType();
                return methodType.GetMethod(mb.Name);
            } else {
                throw new InvalidOperationException();
            }
        }

        internal Delegate CreateDelegate(Type delegateType) {
            MethodInfo method;
            return CreateDelegate(delegateType, out method);
        }

        internal Delegate CreateDelegate(Type delegateType, out MethodInfo method) {
            ContractUtils.RequiresNotNull(delegateType, "delegateType");

            method = CreateDelegateMethodInfo();

            object[] constants = (_constantPool.IsBound) ? _constantPool.Data : null;

            if (constants != null) {
                return ReflectionUtils.CreateDelegate(method, delegateType, new Closure(constants, null));
            } else {
                return ReflectionUtils.CreateDelegate(method, delegateType);
            }
        }

        /// <summary>
        /// Creates a compiler that shares the same characteristic as "this". If compiling into
        /// DynamicMethod (both fake or real), it will create compiler backed by dynamic method
        /// (also fake or real), if compiling into a type, it will create compiler linked to
        /// a new (static) method on the same type.
        /// </summary>
        private LambdaCompiler CreateLambdaCompiler(
            LambdaExpression lambda,
            string name,
            Type retType,
            IList<Type> paramTypes,
            string[] paramNames,
            ConstantPool constantPool,
            bool closure) {

            LambdaCompiler lc;
            if (_dynamicMethod) {
                lc = CreateDynamicLambdaCompiler(_compiler, lambda, name, retType, paramTypes, paramNames, constantPool, closure, _emitDebugSymbols);
            } else {
                lc = CreateStaticLambdaCompiler(_compiler, lambda, _typeGen, name, retType, paramTypes, paramNames, constantPool, closure, _emitDebugSymbols);
            }

            // TODO: better way to flow this in?
            lc._debugSymbolWriter = _debugSymbolWriter;

            return lc;
        }

        private void EndExceptionBlock() {
            if (_targets.Count > 0) {
                Targets t = _targets.Peek();
                Debug.Assert(t.BlockType != TargetBlockType.LoopInFinally);
                if (t.BlockType == TargetBlockType.Finally && t.LeaveLabel.HasValue) {
                    _ilg.MarkLabel(t.LeaveLabel.Value);
                }
            }

            _ilg.EndExceptionBlock();
        }

        #region IL Debugging Support

#if !SILVERLIGHT
        private static DebugILGen CreateDebugILGen(ILGenerator il, TypeGen tg, MethodBase method, IList<Type> paramTypes) {
            TextWriter txt = new StreamWriter(Snippets.Shared.GetMethodILDumpFile(method));
            DebugILGen dig = new DebugILGen(il, tg, txt);

            dig.WriteLine(String.Format("{0} {1} (", method.Name, method.Attributes));
            StringBuilder sb = new StringBuilder();
            foreach (Type type in paramTypes) {
                sb.Length = 0;  // Clear the builder
                sb.Append("\t");
                ReflectionUtils.FormatTypeName(sb, type);
                dig.WriteLine(sb.ToString());
            }
            dig.WriteLine(")");

            return dig;
        }
#endif

        #endregion
        
        // Gets a temporary variable from the generator
        internal Slot NextGeneratorTemp(Type type) {
            Debug.Assert(IsGeneratorBody);

            return CastSlot.Cast(type, GetVariable(_generatorInfo.NextGeneratorTemp()));
        }

        private TryStatementInfo GetTsi(TryStatement node) {
            if (_generatorInfo == null) {
                return null;
            }
            return _generatorInfo.TryGetTsi(node);
        }

        private YieldTarget GetYieldTarget(YieldStatement node) {
            if (_generatorInfo == null) {
                return null;
            }
            return _generatorInfo.TryGetYieldTarget(node);
        }

        #region IDisposable Members

        public void Dispose() {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing) {
        }

        #endregion

        #region Factory methods

        /// <summary>
        /// Creates a compiler backed by dynamic method. Sometimes (when debugging is required) the dynamic
        /// method is actually a 'fake' dynamic method and is backed by static type created specifically for
        /// the one method
        /// </summary>
        private static LambdaCompiler CreateDynamicLambdaCompiler(
            Compiler compiler,
            LambdaExpression lambda,
            string methodName,
            Type returnType,
            IList<Type> paramTypes,
            IList<string> paramNames,
            ConstantPool constantPool,
            bool closure,
            bool emitDebugSymbols) {

            Assert.NotEmpty(methodName);
            Assert.NotNull(returnType);
            Assert.NotNullItems(paramTypes);

            LambdaCompiler lc;

            //
            // Generate a static method if either
            // 1) we want to dump all geneated IL to an assembly on disk (SaveSnippets on)
            // 2) the method is debuggable, i.e. DebugMode is on and a source unit is associated with the method
            //
            if (Snippets.Shared.SaveSnippets || emitDebugSymbols) {
                TypeGen typeGen = Snippets.Shared.DefineType(methodName, typeof(object), false, false, emitDebugSymbols);
                lc = CreateStaticLambdaCompiler(
                    compiler,
                    lambda,
                    typeGen,
                    methodName,
                    returnType,
                    paramTypes,
                    paramNames,
                    constantPool,
                    closure,
                    emitDebugSymbols
                );
            } else {
                Type[] parameterTypes = MakeParameterTypeArray(paramTypes, constantPool, closure);
                DynamicMethod target = Snippets.Shared.CreateDynamicMethod(methodName, returnType, parameterTypes);
                lc = new LambdaCompiler(
                    compiler,
                    lambda,
                    null, // typeGen
                    target,
                    target.GetILGenerator(),
                    parameterTypes,
                    constantPool,
                    closure,
                    emitDebugSymbols
                );
            }

            // this is a dynamic method:
            lc._dynamicMethod = true;

            return lc;
        }

        /// <summary>
        /// Creates a LambdaCompiler backed by a method on a static type (represented by tg).
        /// </summary>
        private static LambdaCompiler CreateStaticLambdaCompiler(
            Compiler compiler,
            LambdaExpression lambda,
            TypeGen tg,
            string name,
            Type retType,
            IList<Type> paramTypes,
            IList<string> paramNames,
            ConstantPool constantPool,
            bool closure,
            bool emitDebugSymbols) {

            Assert.NotNull(name, retType);

            Type[] parameterTypes = MakeParameterTypeArray(paramTypes, constantPool, closure);

            MethodBuilder mb = tg.TypeBuilder.DefineMethod(name, CompilerHelpers.PublicStatic, retType, parameterTypes);
            LambdaCompiler lc = new LambdaCompiler(
                compiler,
                lambda,
                tg,
                mb,
                mb.GetILGenerator(),
                parameterTypes,
                constantPool,
                closure,
                emitDebugSymbols
            );

            if (paramNames != null) {
                // parameters are index from 1, with constant pool we need to skip the first arg
                int startIndex = (constantPool != null || closure) ? 2 : 1;
                for (int i = 0; i < paramNames.Count; i++) {
                    mb.DefineParameter(i + startIndex, ParameterAttributes.None, paramNames[i]);
                }
            }
            return lc;
        }

        #endregion

        #region Utilities

        [Conditional("DEBUG")]
        private static void NoteCompilerCreation(MethodBase mi) {
            string name = mi.Name;

            for (int i = 0; i < name.Length; i++) {
                if (!Char.IsLetter(name[i]) && name[i] != '.') {
                    name = name.Substring(0, i);
                    break;
                }
            }

            PerfTrack.NoteEvent(PerfTrack.Categories.Compiler, "Compiler " + name);
        }

        #endregion
    }
}
